Skip to content

feat(presets): add enable/disable toggle and update semantics#1891

Merged
mnriem merged 14 commits intogithub:mainfrom
mbachorik:feature/preset-update-and-toggle
Mar 19, 2026
Merged

feat(presets): add enable/disable toggle and update semantics#1891
mnriem merged 14 commits intogithub:mainfrom
mbachorik:feature/preset-update-and-toggle

Conversation

@mbachorik
Copy link
Contributor

Summary

  • Add preset enable and preset disable CLI commands for toggling presets without removal
  • Add restore() method to PresetRegistry for rollback scenarios
  • Update get() and list() to return deep copies (prevents accidental mutation)
  • Update list_by_priority() to filter disabled presets by default
  • Disabled presets are skipped during template resolution

Addresses

Defensive Programming

Following feedback from previous PR, this implementation includes:

  • Input validation in restore() for None/non-dict metadata
  • Corrupted registry entry handling in enable/disable commands
  • Deep copy protection to prevent state mutation
  • Tests for all edge cases including corrupted state

Test plan

  • All 386 tests pass
  • Ruff linter clean
  • New tests for restore(), deep copy, enable/disable CLI, corrupted state handling

🤖 Generated with Claude Code

Add preset enable/disable CLI commands and update semantics to match
the extension system capabilities.

Changes:
- Add `preset enable` and `preset disable` CLI commands
- Add `restore()` method to PresetRegistry for rollback scenarios
- Update `get()` and `list()` to return deep copies (prevents mutation)
- Update `list_by_priority()` to filter disabled presets by default
- Add input validation to `restore()` for defensive programming
- Add 16 new tests covering all functionality and edge cases

Closes github#1851
Closes github#1852

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 18, 2026 06:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the preset system to support safer registry mutation patterns (rollback/restore and copy isolation) and adds CLI enable/disable toggles so presets can be temporarily turned off without uninstalling, while ensuring disabled presets are skipped during template resolution.

Changes:

  • Added PresetRegistry.restore() plus deep-copy isolation for PresetRegistry.get() and PresetRegistry.list().
  • Updated PresetRegistry.list_by_priority() to exclude disabled presets by default (with an include_disabled override).
  • Introduced specify preset enable|disable commands and added tests covering restore, deep-copy behavior, disabled filtering, CLI flows, and corrupted-state handling.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/specify_cli/presets.py Adds restore(), deep-copy semantics for registry reads, and default filtering of disabled presets in priority listing (affects resolution).
src/specify_cli/__init__.py Adds preset enable / preset disable Typer commands with project checks and corrupted-state handling.
tests/test_presets.py Adds coverage for restore validation, deep-copy protection, list-by-priority filtering, CLI enable/disable flows, and resolution behavior when disabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Fix error message in restore() to match actual validation ("dict" not "non-empty dict")
- Use copy.deepcopy() in restore() to prevent caller mutation
- Apply same fixes to ExtensionRegistry for parity
- Add /defensive-check command for pre-PR validation
- Add tests for restore() validation and deep copy behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik
Copy link
Contributor Author

Addressed both review comments:

  1. Error message accuracy: Changed from "non-empty dict" to "dict" to match actual validation
  2. Deep copy: Now using copy.deepcopy(metadata) instead of dict(metadata)

Also applied the same fixes to ExtensionRegistry.restore() for parity, and added tests for both.

Added /defensive-check command to catch these issues before PRs.

@mbachorik mbachorik requested a review from Copilot March 18, 2026 08:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset system to support safer registry semantics and operational toggling, bringing presets closer to feature parity with extensions (update/restore patterns and enable/disable behavior).

Changes:

  • Add PresetRegistry.restore() plus defensive validation, and align ExtensionRegistry.restore() behavior (validation + deep copy).
  • Return deep copies from preset registry get()/list() and filter disabled presets by default in list_by_priority().
  • Add specify preset enable/disable commands and corresponding tests, including behavior around disabled preset resolution.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy protections for registry reads, and default filtering of disabled presets in priority ordering (affects resolution).
src/specify_cli/extensions.py Hardens restore() with input validation and deep-copy to avoid caller mutation and align with presets.
src/specify_cli/__init__.py Introduces preset enable/disable CLI commands for toggling preset enabled state.
tests/test_presets.py Adds coverage for preset restore validation, deep-copy behavior, disabled filtering, CLI enable/disable flows, and resolution skipping.
tests/test_extensions.py Adds coverage for extension restore validation and deep-copy behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Add note to enable/disable output clarifying commands/skills remain active
- Add include_disabled parameter to ExtensionRegistry.list_by_priority for parity
- Add tests for extension disabled filtering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset (and related extension) registry semantics to support safer state management and toggling without uninstalling, aligning presets more closely with extensions and improving robustness during rollback/registry-corruption scenarios.

Changes:

  • Add preset enable / preset disable CLI commands to toggle installed presets without removal.
  • Add PresetRegistry.restore() and update get() / list() to return deep copies (defensive against accidental mutation).
  • Update list_by_priority() for presets/extensions to exclude disabled entries by default (with an override to include them).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore() and deep-copy isolation for registry reads; filters disabled presets in priority listing.
src/specify_cli/extensions.py Adds validation/deep-copy to restore() and changes list_by_priority() to exclude disabled by default.
src/specify_cli/__init__.py Introduces specify preset enable/disable commands with corrupted-state handling and user messaging.
tests/test_presets.py Adds unit/integration tests for restore semantics, deep-copy behavior, enable/disable, and disabled preset resolution behavior.
tests/test_extensions.py Adds tests for restore() input validation/deep-copy and for list_by_priority() disabled filtering semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

…entries

- Fix _get_all_extensions_by_priority to use include_disabled=True for tracking
  registered IDs, preventing disabled extensions from being picked up as
  unregistered directories
- Add corrupted entry handling to get() - returns None for non-dict entries
- Add integration tests for disabled extension template resolution
- Add tests for get() corrupted entry handling in both registries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds preset enable/disable toggling and safer registry mutation semantics to better support rollback/update workflows and prevent accidental state mutation during resolution.

Changes:

  • Introduces PresetRegistry.restore() and strengthens registry isolation by returning deep copies from get()/list().
  • Adds specify preset enable|disable commands and filters disabled presets/extensions out of priority-based resolution by default.
  • Extends test coverage for restore validation, deep-copy behavior, corrupted registry entries, and disabled template resolution.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy returns, and default filtering of disabled presets during priority iteration; updates resolver to avoid reintroducing disabled extensions via fallback scan.
src/specify_cli/extensions.py Makes restore() validate input + deep copy; ensures get() handles corrupted entries; adds include_disabled option to list_by_priority() with disabled excluded by default.
src/specify_cli/__init__.py Adds new preset enable / preset disable CLI commands with corrupted-state handling and messaging about what disabling affects.
tests/test_presets.py Adds tests for preset restore/deep-copy/corruption handling, disabled preset resolution behavior, and disabled extension template resolution regressions.
tests/test_extensions.py Adds tests for extension restore validation/deep-copy, corrupted get behavior, and include/exclude disabled behavior in list_by_priority().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Add defensive handling to list() when presets/extensions is not a dict
- Return empty dict instead of crashing on corrupted registry
- Apply same fix to both PresetRegistry and ExtensionRegistry for parity
- Add tests for corrupted registry handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik mbachorik requested a review from Copilot March 18, 2026 19:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset/extension registry semantics to support enable/disable toggles and safer registry mutation patterns (restore/deep-copy), and updates template resolution to skip disabled items.

Changes:

  • Add preset enable / preset disable CLI commands that toggle a preset’s enabled flag without removing it.
  • Add PresetRegistry.restore() and update registry accessors (get(), list(), list_by_priority()) to use deep copies and filter disabled entries by default.
  • Update template resolution to skip disabled presets/extensions (including preventing disabled extensions from being re-introduced via the “unregistered dir scan” fallback).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy protections, and disabled filtering; updates extension resolution logic to respect disabled state.
src/specify_cli/extensions.py Aligns restore() validation/deep-copy and adds disabled filtering + corrupted-registry handling in list().
src/specify_cli/__init__.py Introduces preset enable/disable Typer commands with user messaging and error handling.
tests/test_presets.py Adds coverage for restore/deep-copy/corruption handling, preset enable/disable CLI, and disabled resolution behavior.
tests/test_extensions.py Adds coverage for restore input validation, deep-copy/corruption handling, and disabled filtering semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- get() now validates self.data["presets/extensions"] is a dict before accessing
- restore() ensures presets/extensions dict exists before writing
- Prevents crashes when registry JSON is parseable but has corrupted structure
- Applied same fixes to both PresetRegistry and ExtensionRegistry for parity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik mbachorik requested a review from Copilot March 18, 2026 20:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset/extension registry semantics to support enable/disable toggles, safe rollback via restore(), and defensive deep-copying to prevent accidental in-memory mutation—while ensuring disabled presets/extensions are skipped during template resolution.

Changes:

  • Add PresetRegistry.restore() plus deep-copy isolation in PresetRegistry.get()/list() and default filtering of disabled presets in list_by_priority().
  • Add preset enable / preset disable CLI commands and update template resolution to exclude disabled presets/extensions (including preventing disabled extensions from being picked up via “unregistered directory” fallback).
  • Add/extend test coverage for restore validation, deep-copy behavior, corrupted registry handling, disabled filtering, and disabled template resolution.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy return semantics, and default exclusion of disabled presets during priority listing/resolution.
src/specify_cli/extensions.py Aligns extension registry behavior with presets (restore validation, deep-copy returns, corrupted-entry handling, disabled filtering).
src/specify_cli/__init__.py Introduces specify preset enable/disable commands to flip the registry enabled flag.
tests/test_presets.py Adds tests for restore/deep-copy/corruption, disabled preset filtering, and disabled extension template resolution regressions.
tests/test_extensions.py Adds tests for extension restore validation/deep-copy/corruption and disabled filtering in list_by_priority().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- _load() now validates json.load() result is a dict before returning
- is_installed() validates presets/extensions is a dict before checking membership
- Prevents crashes when registry file is valid JSON but wrong type (e.g., array)
- Applied same fixes to both registries for parity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik mbachorik requested a review from Copilot March 19, 2026 06:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset system to support reversible state changes (enable/disable + restore semantics) and hardens registry access patterns to avoid accidental mutation and improve corrupted-registry resilience, while aligning preset/extension behaviors during template resolution.

Changes:

  • Added specify preset enable|disable commands to toggle presets without uninstalling.
  • Introduced PresetRegistry.restore() and updated registry reads (get(), list()) to return deep copies and handle corrupted entries more safely.
  • Updated priority ordering to exclude disabled presets/extensions by default and ensured disabled extensions are not reintroduced via unregistered-dir scanning during resolution.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy isolation for reads, defensive handling for corrupted entries, and disables presets in list_by_priority() by default; updates extension resolution to respect disabled state.
src/specify_cli/extensions.py Aligns extension registry behavior with presets: dict validation on load, deep-copy reads, defensive corrupted-entry handling, and list_by_priority(include_disabled=...).
src/specify_cli/__init__.py Adds new Typer CLI commands preset enable / preset disable that flip the registry enabled flag with user messaging.
tests/test_presets.py Adds unit + integration tests for restore/deep-copy behavior, corrupted registry handling, enable/disable commands, and resolution skipping for disabled presets/extensions.
tests/test_extensions.py Adds tests for restore input validation, deep-copy behavior, corrupted registry handling, and list_by_priority() disabled filtering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- _load() now normalizes the presets/extensions field to {} if not a dict
- Makes corrupted registries recoverable for add/update/remove operations
- Applied same fix to both registries for parity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik mbachorik requested a review from Copilot March 19, 2026 06:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds safer preset/extension registry mutation semantics and introduces preset enable/disable toggles so presets can be temporarily excluded from template resolution without uninstalling.

Changes:

  • Added PresetRegistry.restore() for rollback scenarios and hardened registry loading against corrupted JSON structures.
  • Updated get()/list() to return deep copies and updated list_by_priority() to exclude disabled entries by default (with an opt-in to include them).
  • Added preset enable / preset disable CLI commands and expanded tests for restore/deep-copy behavior, corrupted registry handling, and disabled template resolution.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_presets.py Adds tests for restore(), deep-copy protection, preset enable/disable CLI, and resolution behavior when presets/extensions are disabled.
tests/test_extensions.py Adds tests for ExtensionRegistry.restore() validation, deep-copy behavior, corrupted entries, and disabled filtering in list_by_priority().
src/specify_cli/presets.py Implements PresetRegistry.restore(), hardens _load(), returns deep copies from get()/list(), adds disabled filtering to list_by_priority(), and updates resolver extension handling.
src/specify_cli/extensions.py Hardens _load(), validates/deep-copies restore(), returns None for corrupted entries in get(), returns {} for corrupted in list(), and adds disabled filtering to list_by_priority().
src/specify_cli/__init__.py Adds specify preset enable/disable CLI commands with user messaging about scope of the toggle.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Use registry.list().keys() instead of list_by_priority() for tracking
- Corrupted entries are now treated as tracked, not picked up as unregistered
- Tighten test assertion for disabled preset resolution
- Update test to match new expected behavior for corrupted entries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik mbachorik requested a review from Copilot March 19, 2026 07:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds preset enable/disable toggles and safer registry mutation semantics (restore + deep-copy isolation), aligning presets with the extension registry’s defensive patterns and ensuring disabled items are excluded from template resolution.

Changes:

  • Add specify preset enable|disable commands to toggle presets without uninstalling.
  • Add PresetRegistry.restore() and harden registry access (_load normalization, deep-copying get()/list(), corruption handling).
  • Exclude disabled presets/extensions from list_by_priority() by default and ensure template resolution skips disabled items (with regression/integration tests).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_presets.py Adds tests for PresetRegistry.restore(), deep-copy behavior, corrupted-state handling, and disabled preset/extension resolution behavior.
tests/test_extensions.py Extends extension registry tests for restore validation, deep-copy/corruption behavior, disabled filtering, and resolver regression around corrupted entries.
src/specify_cli/presets.py Implements preset registry restore + deep-copy isolation, adds disabled filtering to priority listing, and adjusts resolver extension handling to avoid “unregistered dir” fallback for disabled/corrupted entries.
src/specify_cli/extensions.py Hardens extension registry load/restore/get/list/list_by_priority with corruption handling and disabled filtering by default.
src/specify_cli/init.py Adds preset enable / preset disable Typer commands and keeps preset list output showing enabled/disabled status.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Add defensive check for corrupted metadata in remove()
- Match existing pattern in PresetManager.remove()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik mbachorik requested a review from Copilot March 19, 2026 07:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset/extension registries and CLI to support enable/disable toggles and safer registry mutation patterns (restore + deep-copy isolation), and updates template resolution to skip disabled items by default.

Changes:

  • Add PresetRegistry.restore() (and strengthen ExtensionRegistry.restore()) with input validation and deep-copy isolation.
  • Make get()/list() return deep copies; make list_by_priority() exclude disabled entries by default (with include_disabled=True escape hatch).
  • Add specify preset enable|disable commands and update template resolution to skip disabled presets/extensions (including unregistered-dir fallback).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore + deep-copy protections; filters disabled items in priority listing; updates resolver extension tracking to avoid disabled/corrupted entries being picked up as “unregistered”.
src/specify_cli/extensions.py Adds registry load normalization, restore validation/deep-copy, defensive get/list behavior, and disabled filtering in list_by_priority().
src/specify_cli/__init__.py Introduces preset enable / preset disable CLI commands.
tests/test_presets.py Adds coverage for restore validation/deep copy, corrupted registry handling, disabled preset resolution behavior, and CLI enable/disable flows.
tests/test_extensions.py Adds coverage for restore validation/deep copy, corrupted registry handling, disabled filtering, and resolution regression around corrupted/unregistered scanning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

iamaeroplane and others added 2 commits March 19, 2026 08:54
- Add lightweight keys() method that returns IDs without deep copy
- Update list() to filter out non-dict entries (match type contract)
- Use keys() instead of list().keys() for performance
- Fix comment to reflect actual behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… parity

- Extension enable/disable: use delta pattern matching presets
- add(): use copy.deepcopy(metadata) in both registries
- remove(): guard outer field for corruption in both registries
- update(): guard outer field for corruption in both registries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 19, 2026 08:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds safer preset/extension registry semantics and CLI toggles so presets/extensions can be enabled/disabled without uninstalling, while hardening registry access against corruption and accidental mutation.

Changes:

  • Add preset enable / preset disable commands and update template resolution to skip disabled presets/extensions.
  • Add restore() to PresetRegistry (and extend ExtensionRegistry.restore() validation) plus keys() helpers to avoid corrupted/disabled entries being treated as “unregistered”.
  • Make registry get()/list() return deep copies and add tests for deep-copy behavior and corrupted-registry edge cases.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Registry hardening (restore, deep-copy get/list, disabled filtering) + resolver changes to avoid disabled/corrupted extensions being resolved.
src/specify_cli/extensions.py Registry hardening (restore validation, deep-copy get/list, disabled filtering, keys() helper).
src/specify_cli/__init__.py Adds preset enable/disable commands; adjusts extension enable/disable to update only the enabled field.
tests/test_presets.py Adds coverage for restore validation, deep-copy isolation, disabled preset resolution behavior, and disabled/corrupted extension resolution behavior.
tests/test_extensions.py Adds coverage for restore validation/deep-copy/corruption handling and disabled filtering semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@mbachorik mbachorik requested a review from Copilot March 19, 2026 09:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the presets system to support safer registry mutation patterns and an enable/disable toggle, aligning preset behavior more closely with extensions and making template resolution respect disabled state.

Changes:

  • Added preset enable / preset disable CLI commands to toggle presets without uninstalling.
  • Added restore() to PresetRegistry and hardened both preset/extension registries against corrupted on-disk state (deep-copy reads, filtering invalid entries, defensive _load()).
  • Updated priority-based resolution to exclude disabled presets/extensions by default, with explicit opt-in to include them when needed.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds defensive registry loading, deep-copy isolation, restore() and keys(), and excludes disabled presets from list_by_priority() by default; updates extension-resolution logic to avoid “unregistered dir” fallback for disabled/corrupted extensions.
src/specify_cli/extensions.py Mirrors the defensive registry behaviors (deep copies, restore() validation, keys(), exclude disabled by default in list_by_priority()), and hardens removal logic when metadata is missing/corrupted.
src/specify_cli/__init__.py Introduces preset enable/disable commands and adjusts extension enable/disable to update only the enabled field via partial registry updates.
tests/test_presets.py Adds coverage for preset restore(), deep-copy behavior, corrupted registry handling, preset enable/disable CLI, and disabled preset/extension resolution behavior.
tests/test_extensions.py Adds/adjusts tests for extension restore() validation, corrupted registry behavior, and default exclusion of disabled extensions from priority lists.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Both PresetRegistry.update() and ExtensionRegistry.update() now deep
copy the input updates/metadata dict to prevent callers from mutating
nested objects after the call.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik
Copy link
Contributor Author

Ready for review.

@mbachorik mbachorik marked this pull request as ready for review March 19, 2026 11:09
@mbachorik mbachorik requested a review from mnriem as a code owner March 19, 2026 11:09
Copilot AI review requested due to automatic review settings March 19, 2026 11:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds enable/disable semantics and safer mutation patterns to the preset (and extension) registries, ensuring disabled presets/extensions are excluded from template resolution while protecting registry state from accidental caller mutation.

Changes:

  • Add specify preset enable|disable commands to toggle a preset’s enabled flag without uninstalling.
  • Add PresetRegistry.restore() and harden both registries against corrupted on-disk/in-memory state (type validation, non-dict entry handling).
  • Make registry reads return deep copies and update resolution ordering to skip disabled presets/extensions by default.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy isolation for registry reads/writes, keys(), and default exclusion of disabled presets; updates resolver to avoid disabled/corrupted extension fallback resolution.
src/specify_cli/extensions.py Mirrors registry hardening and deep-copy semantics; adds keys() and default exclusion of disabled extensions in list_by_priority().
src/specify_cli/__init__.py Introduces preset enable/disable commands; adjusts extension enable/disable to use partial update() payloads.
tests/test_presets.py Adds coverage for restore(), deep-copy behavior, corrupted-entry behavior, preset enable/disable CLI, and resolution skipping for disabled presets/extensions.
tests/test_extensions.py Adds parity tests for restore validation/deep-copy, corrupted-entry behavior, and list-by-priority disabled filtering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@mnriem mnriem merged commit 2bf655e into github:main Mar 19, 2026
11 of 12 checks passed
@mnriem
Copy link
Collaborator

mnriem commented Mar 19, 2026

Thank you!

kanfil added a commit to tikalk/agentic-sdlc-spec-kit that referenced this pull request Mar 19, 2026
Upstream changes merged:
- feat: migrate Codex/agy init to native skills workflow (github#1906)
- feat(presets): add enable/disable toggle and update semantics (github#1891)
- feat: add iFlow CLI support (github#1875)
- feat(commands): wire before/after hook events into specify and plan templates (github#1886)
- Add conduct extension to community catalog (github#1908)
- feat(extensions): add verify-tasks extension to community catalog (github#1871)

Tikalk customizations preserved:
- Orange theme (ACCENT_COLOR = #f47721)
- Pre-Installed Extensions panel in init output
- --team-ai-directives CLI parameter
- /spec.* command aliases (instead of /speckit.*)
- Skills package manager (specify skill subcommand)
- Bundled extensions/presets installation
- install_bundled_extensions, install_bundled_presets, get_preinstalled_extensions functions

Version: 0.1.15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preset system: Add enable/disable toggle functionality Preset system: Add update semantics (update/restore methods)

3 participants